
""" The Galaxy class is the interface to almost all the game data (or it is until 
I figure out its bad or a better way to do it). IT takes args for the width and height
of the map grid, and how dense the starfield will be. The class (upon init) generates 
the map and uses the generated map to populate a list with Star objects. Each Star 
has its own internals of planet generation etc.
"""

from random import *
from Star import *
from Planet import *
from Ksink import *

from math import sqrt #For Galaxy.__init__() ADV 5-12-07

class Galaxy:
        def __init__(self, w_, h_, dense_=0):
                # calles the loadNames() function in Ksink.
                self.starNames = loadNames()
                
                self.sizeW = w_
                self.sizeH = h_

                self.myStarPoints = starPoints(self.sizeW,self.sizeH,sqrt(self.sizeH * self.sizeW))
                self.map = self.myStarPoints.exportMap()
                # generate the galaxy map and store it in a temp var
                #map = generateMap(w_, h_, dense_)
                
                self.stars = [] #ADV This list should probably be replaced with a class...
                #self.stars = populateMap(w_, h_, map, self.starNames)
                for point in self.myStarPoints.getList():
                        self.stars.append(Star(point[0], point[1], self.myStarPoints.getList(), getName(self.starNames)))
        
        # Method to return a star from a map location
        # TODO: finish the meat of this code
        def     getStar(self, x_, y_):
                tempPos = (x_,y_)
                for star in stars:
                        if star.pos == temp.pos:
                                return star
                        else:
                                return None
                
        # Used to simulate a multi-dimensional array. can get the value of a flat list as if it had x/y cords   
        def getMapLoc(self, x_, y_):
                return (x_+(y_*self.getHeight()))
        
        # returns the width of the map.
        def getWidth(self):
                return self.sizeW
        
        # returns the height of the map
        def getHeight(self):
                return self.sizeH               
        
        # Dumps the map to the screen along with data on each star and its planets.
        def dump(self):
                print "map info: size: [", self.getWidth(), ",", self.getHeight(), "] length: ", len(self.map)
                x = 0
                y = 0
                while y < self.sizeH:
                        while x < self.sizeW:
                                print self.map[self.getMapLoc(x,y)],
                                x += 1
                        print ""
                        x = 0
                        y += 1
                printTotals(self)
                
        #delete me:
        #def generateMapNew(self, w_, h_, totalStars_):
                #map = starMap(w_, h_, totalStars_)
                

                
class starPoints:
        def __init__(self, w_, h_, totalStars_, maxEstrangement_=5, attempts_=1000):
                if w_ < 4 or h_ < 4:
                        raise StandardError("Too small of a map!")
                if (w_-2)*(h_-2) < totalStars_:
                        raise StandardError("Too many stars!")


                self.height = h_
                self.width = w_
                
                self.maxEstrangement_ = maxEstrangement_

                for iteration in range(attempts_):
                        #print "Iteration",iteration
                        self.starPointList = []
                        #For each star to be placed
                        for i in range(totalStars_):
                                #Until this star is placed
                                while len(self.starPointList) <= i:
                                        newPointCandidate = (randint(1,w_-1),randint(1,h_-1))
                                        if self.starPointList.count(newPointCandidate) == 0:
                                                self.starPointList.append(newPointCandidate)
                        if self.isSane():
                                #print "Iteration",iteration + 1,"is sane."
                                break
                        if iteration == attempts_ - 1:
                                raise StandardError('Could not generate a sane galaxy in ',attempts_,' attempts.')

        def getList(self):
                """Returns the list of (x,y) tuples of stars."""
                return self.starPointList

        def exportMap(self):
                """Returns a list of values, 0 or 1-100, for each point on map, denoting existance of star.
                Replaces the generateMap() function."""
                mapList = []

                #For each potential location...
                for row in range(self.height):
                        for column in range(self.width):
                                #...if this location is in the starPointList...
                                if self.starPointList.count((column,row)) > 0:
                                        #...then add it to the list to be exported.
                                        mapList.append(randint(1, 100))
                                else:
                                        #otherwise, add a zero placeholder.
                                        mapList.append(0)
                return mapList
                        
        def getDistance(self, pointAlpha, pointOmega):
                """Returns the distance between two star points"""
                return abs(pointAlpha[0] - pointOmega[0]) + abs(pointAlpha[1] - pointOmega[1]) / 2

        def isSane(self):
                """Returns True if this map's layout is suitable for play, False otherwise."""
                #Make a copy of our starPointList so that we can "pop" elements out of it.
                starPointListCopy = self.starPointList[:] #the [:] assigns a copy instead of a reference. -ADV
                #Make another list that will reflect starPointList, containing 1 if it's double is estranged, 0 otherwise.
                starPointEstrangedList = []
                #Populate starPointEstrangedList with 1's that will later be clobbered with 0'
                for aPoint in starPointListCopy:
                        starPointEstrangedList.append(1)
                for thisPoint in starPointListCopy:
                        starPointListCopy.remove(thisPoint)
                        for thatPoint in starPointListCopy:
                                if self.getDistance(thisPoint,thatPoint) <= self.maxEstrangement_:
                                        #print thisPoint,"is close enough to",thatPoint,"-",self.getDistance(thisPoint,thatPoint),"parsecs"
                                        starPointEstrangedList[self.starPointList.index(thisPoint)] = 0
                                        starPointEstrangedList[self.starPointList.index(thatPoint)] = 0
                                        #print thisPoint,self.starPointList.count(thisPoint),thatPoint,self.starPointList.count(thatPoint)
                #print starPointEstrangedList
                if starPointEstrangedList.count(1) == 0:
                        #print "No stars are estranged. :)"
                        return True
                else:
                        #print starPointEstrangedList.count(1),"stars are estranged!"
                        return False

                #Now to make sure they aren't clumped...

# generate map returns an array of len(w*h) of mostly 0s unless the random number generated is between 1 and dense_
#ADV - This is now obsolete.
def generateMap(w_, h_, dense_):
        
        thresh = 0
        map = []
        while thresh <= dense_*5:
                thresh = 0
                size = w_*h_
                x = 0 
                while x <= size:
                        randomNumber = randint(1, 100)
                        if randomNumber <= dense_: 
                                map.append(randomNumber)
                                thresh +=1
                        else: map.append(0)
                        x += 1  
        print "thresh", thresh
        return map                        
        

# Takes the list of map cords and populates the list of stars with real Star objects.
#Deprecated ADV 5-12-07
def populateMap(w_, h_, map_, names_):
        starsList = []
        stars = []
        x = 0
        y = 0
        while y < h_:
                while x < w_:
                        t = x+(y*h_)
                        #print t
                        if map_[t]:
                                print "Star found @:", x, y
                                starsList.append((x, y))
                        x += 1
                x = 0
                y += 1
        for item in starsList:
                stars.append(Star(item[0], item[1], starsList, getName(names_)))
        return stars

# Prints the totals of the planets and stars, 
def printTotals(galaxy_):
        totalPlanets = 0
        totalStars = 0
        for s in galaxy_.stars:
                totalStars += 1
                print s.name, "@", s.x, s.y, "Closest:",
                for c in s.closest:
                        print c,
                print ""
                totalPlanets += len(s.planets)
                print "Planets:", len(s.planets)
        print "Total Planets:", totalPlanets
        print "Total Stars", totalStars
        
